/*
 * PeerCheckTasks - TimerTask that checks for good/bad up/downloaders. Copyright
 * (C) 2003 Mark J. Wielaard
 * 
 * This file is part of Snark.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.klomp.snark;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * TimerTask that checks for good/bad up/downloader. Works together with the
 * PeerCoordinator to select which Peers get (un)choked.
 */
class PeerCheckerTask extends TimerTask
{
    private static final long KILOPERSECOND = 1024 * (PeerCoordinator.CHECK_PERIOD / 1000);

    private final PeerCoordinator coordinator;
    static final HashMap<Peer, Long> peerUploadAmountMap = new HashMap<Peer, Long>();
    static final HashMap<Peer, Long> peerDownloadAmountMap = new HashMap<Peer, Long>();
    private int count = 0;
    private double lastDownUpRatio = 0.0d;

    PeerCheckerTask (PeerCoordinator coordinator)
    {
        this.coordinator = coordinator;
    }

    @Override
    public void run ()
    {
    	++count;
    	//System.out.println("PeerChecker [" + count + "]");
        synchronized (coordinator.peers) {
        	ArrayList<Peer> allPeers = new ArrayList<Peer>();
        	
        	long totalDownloaded = 0;
        	long totalUploaded = 0;
        	double downUpRatio = 0.0d;
        	
            Iterator<Peer> it = coordinator.peers.iterator();
            // first choke everyone, then we unchoke the best
            while(it.hasNext()) {
            	Peer peer = it.next();

                // Remove dying peers
                if (!peer.isConnected()) {
                    it.remove();
                    continue;
                }
                
                totalDownloaded += peer.getDownloaded();
                totalUploaded += peer.getUploaded();
                
            	peer.setChoking(false);
            	allPeers.add(peer);
            }
            
            if(totalUploaded <= 0) {
            	downUpRatio = (double)totalDownloaded;
            }
            else {
            	downUpRatio = (double)totalDownloaded / (double)totalUploaded;
            }
            
            // if adding another uploader last time caused us a big loss (proportion of this dl/ul to last times)
            if(downUpRatio < lastDownUpRatio) {
            	/*System.out.println("Increasing our max uploaders last time resulted in poorer download-upload " +
            			"proportion. Decreasing max uploaders");*/
            	// decrement active set	
            	PeerCoordinator.MAX_UPLOADERS--;
            } 
            else {
            	// increment active set
            	PeerCoordinator.MAX_UPLOADERS++;
            }
            
            
            ArrayList<Peer> bestUnchoked = new ArrayList<Peer>();
            // unchoke the top MAX_UPLOADERS - 1 peers (minus 1 to reserve a space for optimistic)
            for(Peer peer : allPeers) {            	
                // XXX - We should calculate the up/download rate a bit
                // more intelligently
                long upload = peer.getUploaded();
                long download = peer.getDownloaded();
            
                //System.out.println("PeerChecker - Uploaded [" + upload + "] to peer [" + peer.getPeerID());
                peerUploadAmountMap.put(peer, upload);
                //System.out.println("PeerChecker - Downloaded [" + download + "] from peer [" + peer.getPeerID());
                peerDownloadAmountMap.put(peer, download);
                
                // if peer gave us nothing, choke them by simply continuing to next peer
                // remember we choked everyone to begin with 
                if(download <= 0) {
                	/*System.out.println("Choking peer [" + peer.getPeerID() + "] because they gave us nothing this past " +
                			"round");*/
                    continue;
                }
                
                // if we gave more than we got from the peer, choke them by simply continuing to next peer
                // remember we choked everyone to begin with
                if(upload > download) {
                	/*System.out.println("Choking peer [" + peer.getPeerID() + "] because they gave us less than we gave " +
                			"them this past round");*/
                    continue;
                }
                
                peer.resetCounters();

                log.log(Level.FINEST, peer + ":" + " ul: " + upload
                    / KILOPERSECOND + " dl: " + download / KILOPERSECOND
                    + " i: " + peer.isInterested() + " I: "
                    + peer.isInteresting() + " c: " + peer.isChoking() + " C: "
                    + peer.isChoked());
                
                // unchoke our favorite peers
                // if we have room to add this peer
                if(bestUnchoked.size() < PeerCoordinator.MAX_UPLOADERS - 1) {
                	if(peer.isInterested()) {
                		peer.setChoking(false);
                		bestUnchoked.add(peer);
                	}
                }
                // if we don't have room to add this peer, compare worst to this one and replace if needed
                else {
                	// get worst of best
                	if(bestUnchoked.size() > 0) {
	                	Peer worstOfBest = bestUnchoked.get(0); 
	                	for(int i = 1; i < bestUnchoked.size(); i++) {
	                		Peer p = bestUnchoked.get(i);
	                		if(p.getDownloaded() < worstOfBest.getDownloaded()) {
	                			worstOfBest = p;
	                		}
	                	}
	                	
	                	// compare worst of best download amount to this peer's
	                	if(download > worstOfBest.getDownloaded()) {
	                    	// replace worst of best if this peer gave us more
	                		bestUnchoked.remove(worstOfBest);
	                		peer.setChoking(false);
	                		bestUnchoked.add(peer);
	                	}
                	}
                }
            }
            
            // Resync actual uploaders value
            // (can shift a bit by disconnecting peers)
            coordinator.uploaders = bestUnchoked;
            
            // optimistically unchoke a random interested peer
            coordinator.optimisticUnchokePeer();
            
            lastDownUpRatio = downUpRatio;
        }
    }

    protected static final Logger log = Logger.getLogger("org.klomp.snark.peer");
}
